package com.ficus.road.maintain.core.infrastructure.mongo;

import com.ficus.road.maintain.core.infrastructure.mongo.converter.*;
import com.mongodb.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;


@Slf4j
@Configuration
public class MongoDbConfig extends AbstractMongoConfiguration {

    private final ApplicationContext applicationContext;

    private final MongoProperties properties;

    public MongoDbConfig(ApplicationContext applicationContext,
                         @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") MongoProperties properties) {

        this.applicationContext = applicationContext;
        this.properties = properties;
    }

    @Override
    @Bean
    public MongoDbFactory mongoDbFactory() {
        return new SimpleMongoDbFactory(mongoClientURI());
    }

    @Override
    @Bean
    public MongoTemplate mongoTemplate() {

        log.debug(" instantiating MongoDbFactory ");

        SimpleMongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClientURI());

        return new MongoTemplate(mongoDbFactory, mappingMongoConverter());
    }

    @Bean
    public MongoClientOptions mongoClientOptions() {
        return MongoClientOptions.builder()
                .connectionsPerHost(10)
                .socketTimeout(2000)
                .connectTimeout(2000)
                .serverSelectionTimeout(3000)
                .build();
    }

    @Override
    @Bean
    public MongoClient mongoClient() {

        List<ServerAddress> servers = new ArrayList<>();
        servers.add(new ServerAddress(properties.getHost(), properties.getPort()));

        if (!StringUtils.isEmpty(properties.getUsername()) && !StringUtils.isEmpty(properties.getPassword())) {
            MongoCredential cred = MongoCredential.createCredential(
                    properties.getUsername(),
                    properties.getDatabase(),
                    properties.getPassword()
            );
            return new MongoClient(servers, cred, mongoClientOptions());
        } else {
            return new MongoClient(servers, mongoClientOptions());
        }
    }

    @Bean
    public MongoClientURI mongoClientURI() {
        log.debug(" creating connection with mongodb with uri [{}] ", properties.getUri());
        return new MongoClientURI(properties.getUri());
    }

    @Override
    protected String getDatabaseName() {
        return properties.getDatabase();
    }

    @Override
    @Bean
    public CustomConversions customConversions() {
        List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
        converters.add(new EnumConverterFactory.EnumToIntegerConverter());
        // this is a dummy registration , actually it's a work-around because
        // spring-mongodb doesnt has the option to reg converter factory.
        // so we reg the converter that our factory uses.
        converters.add(new EnumConverterFactory.IntegerToEnumConverter(null));
        converters.add(new BinaryConverter());
        converters.add(new ObjectIdConverter());
        converters.add(new PatternConverter());
        //converters.add(new BooleanConverter());
        converters.add(new BsonUndefinedToNullObjectConverter());
        return new CustomConversions(CustomConversions.StoreConversions.NONE, converters);
    }

    @Override
    @Bean
    public MappingMongoConverter mappingMongoConverter() {
        MongoMappingContext mappingContext = new MongoMappingContext();
        mappingContext.setApplicationContext(applicationContext);

        if (properties.getFieldNamingStrategy() != null) {
            mappingContext.setFieldNamingStrategy(
                    (FieldNamingStrategy) BeanUtils.instantiateClass(properties.getFieldNamingStrategy())
            );
        }

        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());

        MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mappingContext);
        mongoConverter.setCustomConversions(customConversions());
        ConversionService conversionService = mongoConverter.getConversionService();
        ((GenericConversionService) conversionService).addConverterFactory(new EnumConverterFactory());
        mongoConverter.afterPropertiesSet();

        return mongoConverter;
    }

}
